<?php

namespace seecms\db;

use Closure;
use seecms\db\traits\SeeDbExpressWhere;
use seecms\db\traits\SeeDbPolymerization;
use seecms\db\traits\SeeDbSelectMethod;
use PDO;

class SeeDbQuery
{
    use SeeDbExpressWhere;

    use SeeDbSelectMethod;

    use SeeDbPolymerization;

    /**
     * DB链接实例
     *
     * @var SeeDbConnection
     */
    protected $connection = null;

    /**
     * SQL构造实例
     *
     * @var SeeDbBuilder
     */
    protected $builder = null;

    /**
     * 查询表
     *
     * @var string
     */
    protected $table;

    /**
     * 查询条件
     *
     * @var array
     */
    protected $options = [];

    /**
     * 参数绑定标识位
     *
     * @var array
     */
    protected $bind = [];

    /**
     * 更新字段
     * @var string
     */
    protected $created_field = 'created_at';

    /**
     * 更新字段类型
     * @var string
     */
    protected $created_formatter = 'datetime';

    /**
     * 更新字段
     * @var string
     */
    protected $updated_field = 'updated_at';

    /**
     * 更新字段类型
     * @var string
     */
    protected $updated_formatter = 'datetime';

    /**
     * 主键
     * @var string
     */
    protected $pk = '';

    /**
     * 构造方法
     *
     * @param SeeDbConnection $connection 链接实例
     */
    public function __construct(SeeDbConnection $connection)
    {
        $this->connection = $connection;
        $this->builder = $this->getBuilder();
    }

    /**
     * 获取当前数据表的主键
     * @access public
     * @return string|array
     */
    public function getPk()
    {
        if (empty($this->pk)) {
            $this->pk = $this->connection->getPk($this->table);
        }

        return $this->pk;
    }

    /**
     * 查询参数批量赋值
     * @access protected
     * @param array $options 表达式参数
     * @return $this
     */
    protected function options(array $options): SeeDbQuery
    {
        $this->options = $options;

        return $this;
    }

    /**
     * 设置当前的查询参数
     * @access public
     * @param string $option 参数名
     * @param mixed $value 参数值
     * @return $this
     */
    public function setOption(string $option, $value): SeeDbQuery
    {
        $this->options[$option] = $value;

        return $this;
    }

    /**
     * 获取当前的查询参数
     *
     * @param string $name 参数名称
     * @return mixed 查询参数
     */
    public function getOptions(string $name = '')
    {
        if ($name === '') {
            return $this->options;
        } else {
            return $this->options[$name] ?? null;
        }
    }

    /**
     * 去除查询参数
     * @access public
     * @param string $option 参数名 留空去除所有参数
     * @return $this
     */
    public function removeOption(string $option = ''): SeeDbQuery
    {
        if ('' === $option) {
            $this->options = [];
            $this->bind = [];
        } elseif (isset($this->options[$option])) {
            unset($this->options[$option]);
        }

        return $this;
    }


    /**
     * 执行查询 返回数据集
     *
     * @param string $sql sql指令
     * @param array $bind 参数绑定
     * @param bool|string $class 指定返回的数据集对象
     * @return mixed 数据集
     */
    public function query(string $sql, array $bind = [], $class = false)
    {
        return $this->connection->query($sql, $bind, $class);
    }

    /**
     * 执行语句
     *
     * @param string $sql sql指令
     * @param array $bind 参数绑定
     * @return integer 数据集影响行数
     */
    public function execute(string $sql, array $bind = [])
    {
        return $this->connection->execute($sql, $bind);
    }

    /**
     * 获取最近插入的ID
     *
     * @param string $sequence 自增序列名
     * @return integer 最后写入的ID
     */
    public function getLastInsID($sequence = null)
    {
        return $this->connection->getLastInsID($sequence);
    }

    /**
     * 获取最近一次查询的sql语句
     *
     * @return string 最后执行的SQL
     */
    public function getLastSql()
    {
        return $this->connection->getLastSql();
    }

    /**
     * 启动事务
     *
     * @return void
     */
    public function startTrans()
    {
        $this->connection->startTrans();
    }

    /**
     * 用于非自动提交状态下面的查询提交
     *
     * @return void
     */
    public function commit()
    {
        $this->connection->commit();
    }

    /**
     * 事务回滚
     *
     * @return void
     */
    public function rollback()
    {
        $this->connection->rollback();
    }

    /**
     * 事务处理，采用回调函数实现
     *
     * @param Closure $callback 回调函数
     * @return mixed 结果集
     */
    public function action(Closure $callback)
    {
        // 开启事务
        $this->startTrans();
        try {
            $result = null;
            if (is_callable($callback)) {
                // 执行匿名回调
                $result = call_user_func($callback, $this);
            }

            $this->commit();
            return $result;
        } catch (\Exception|\Throwable $e) {
            $this->rollback();
            throw $e;
        }
    }

    /**
     * 启动XA事务
     *
     * @param string $xid XA事务id
     * @return void
     */
    public function startTransXA($xid)
    {
        $this->connection->startTransXA($xid);
    }

    /**
     * 提交XA事务
     *
     * @param string $xid XA事务id
     * @return void
     */
    public function commitXA(string $xid)
    {
        $this->connection->commitXA($xid);
    }

    /**
     * XA事务回滚
     *
     * @param string $xid XA事务id
     * @return void
     */
    public function rollbackXA(string $xid)
    {
        $this->connection->rollbackXA($xid);
    }

    /**
     * 预编译XA事务
     *
     * @param string $xid XA事务id
     * @return void
     */
    public function prepareXA(string $xid)
    {
        $this->connection->prepareXA($xid);
    }

    /**
     * XA事务处理
     *
     * @param Closure $callback 回调函数
     * @param array $dbs 回调函数中涉及使用的数据库连接实例列表
     * @return mixed 结果期
     * @see 注意：使用XA事务无法使用本地事务及锁表操作，更无法支持事务嵌套
     */
    public function actionXA(Closure $callback, array $dbs = [])
    {
        $xids = [];
        $prepareXA = [];
        if (empty($dbs)) {
            $dbs[] = $this->connection;
        }

        // 所有链接实例都需要开启XA事务
        foreach ($dbs as $k => $db) {
            $prepareXA[$k] = false;
            $xids[$k] = uniqid('mon_xa');
            $db->startTransXA($xids[$k]);
        }

        try {
            $result = null;
            if (is_callable($callback)) {
                // 执行匿名回调
                $result = call_user_func($callback, $this);
            }

            // 所有链接实例都需要预编译XA事务
            foreach ($dbs as $k => $db) {
                if (!$prepareXA[$k]) {
                    $db->prepareXA($xids[$k]);
                    $prepareXA[$k] = true;
                }
            }

            // 所有链接实例都需要提交XA事务
            foreach ($dbs as $k => $db) {
                $db->commitXA($xids[$k]);
            }
            return $result;
        } catch (\Exception $e) {
            // 所有链接实例都需要预编译XA事务
            foreach ($dbs as $k => $db) {
                if (!$prepareXA[$k]) {
                    $db->prepareXA($xids[$k]);
                    $prepareXA[$k] = true;
                }
            }
            // 所有链接实例都需要回滚XA事务
            foreach ($dbs as $k => $db) {
                $db->rollbackXA($xids[$k]);
            }
            throw $e;
        } catch (\Throwable $e) {
            // 所有链接实例都需要预编译XA事务
            foreach ($dbs as $k => $db) {
                if (!$prepareXA[$k]) {
                    $db->prepareXA($xids[$k]);
                    $prepareXA[$k] = true;
                }
            }
            // 所有链接实例都需要回滚XA事务
            foreach ($dbs as $k => $db) {
                $db->rollbackXA($xids[$k]);
            }
            throw $e;
        }
    }


    /**
     * 检索或者创建数据
     * @param array $where
     * @param array $params
     * @return array|false|int|\PDOStatement|string
     * @throws SeeDbException
     */
    public function firstOrCreate(array $where, array $params = [])
    {
        $data = $this->where($where)->find();

        if (empty($data)) {
            return $this->table($this->table)->insert(array_merge($where, $params));
        }

        return $data;
    }

    /**
     * 更新或者创建数据
     * @param array $where
     * @param array $params
     * @return false|int
     * @throws SeeDbException
     */
    public function updateOrCreate(array $where, array $params = [])
    {
        $data = $this->where($where)->find();

        if (empty($data)) {
            return $this->table($this->table)->insert(array_merge($where, $params));
        }

        return $this->table($this->table)->update($params, $where);
    }

    /**
     * 新增或者保存数据
     * @param array $data
     * @return false|int
     * @throws SeeDbException
     */
    public function save(array $data = [])
    {
        $id = $data['id'] ?? '';
        if ($id) {
            unset($data['id']);
            return $this->update($data, [
                'id' => $id
            ]);
        } else {
            return $this->insert($data);
        }
    }

    /**
     * 设置自动更新字段
     * @param string|null $field
     * @param string $formatter
     * @return SeeDbQuery
     */
    public function setAutoUpdated(string $field = null, string $formatter = 'datetime'): SeeDbQuery
    {
        $this->updated_field = $field;
        $this->updated_formatter = $formatter;

        return $this;
    }

    /**
     * 设置自动添加创建时间字段
     * @param string|null $field
     * @param string $formatter
     * @return SeeDbQuery
     */
    public function setAutoCreated(string $field = null, string $formatter = 'datetime'): SeeDbQuery
    {
        $this->created_field = $field;
        $this->created_formatter = $formatter;

        return $this;
    }

    /**
     * 更新数据
     *
     * 支持调用方式
     * :where()->update
     * ->update($data,$where)
     * @param array $data 更新的数据
     * @return integer  影响行数
     * @throws SeeDbException
     */
    public function update(array $data = [], array $where = null)
    {
        if ($where) {
            $this->where($where);
        }

        $options = $this->parseExpress();

        if (empty($options['where'])) {
            // 更新操作，查询条件不能为空
            throw new SeeDbException(
                "The update operation query condition cannot be empty!",
                SeeDbException::WHERE_IS_NULL
            );
        }
        $data = array_merge($options['data'], $data);

        if ($this->updated_field) {
            if ($this->updated_formatter == 'datetime') {
                $data[$this->updated_field] = date('Y-m-d H:i:s');
            } else {
                $data[$this->updated_field] = time();
            }
        }


        // 生成sql
        $sql = $this->builder->update($data, $options);
        // $data未空，生成空sql语句
        if ($sql == '') {
            throw new SeeDbException(
                "The generated query statement is empty!",
                SeeDbException::SQL_IS_NULL
            );
        }
        // 获取绑定值
        $bind = $this->getBind();
        // 判断调试模式,返回sql
        if (isset($options['debug']) && $options['debug']) {
            return $this->connection->getRealSql($sql, $bind);
        }
        $result = $this->execute($sql, $bind);
        // 触发更新事件
        SeeDb::trigger('update', $this->connection, $options);

        return $result;
    }

    /**
     * 字段自增
     *
     * @param string|array $field 字段名
     * @param float $step 步长
     * @return integer 影响行数
     */
    public function setInc($field, $step = 1): int
    {
        return $this->inc($field, $step)->update();
    }

    /**
     * 字段自减
     *
     * @param string|array $field 字段名
     * @param float $step 步长
     * @return integer 影响行数
     */
    public function setDec($field, $step = 1): int
    {
        return $this->dec($field, $step)->update();
    }

    /**
     * 插入操作, 默认返回影响行数
     *
     * @param array $data 插入数据
     * @param boolean $replace 是否replace
     * @param boolean $getLastInsID 返回自增主键ID
     * @param string|null $key 自增主键名
     * @return integer 影响行数或自增ID
     * @throws SeeDbException
     */
    public function insert(array $data = [], bool $replace = false, bool $getLastInsID = false, string $key = null)
    {
        $options = $this->parseExpress();
        $data = array_merge($options['data'], $data);
        if (empty($data)) {
            // 操作操作，查询条件不能为空
            throw new SeeDbException(
                "Inserting data cannot be empty",
                SeeDbException::INSERT_DATA_NULL
            );
        }

        if ($this->created_field) {
            if ($this->created_formatter == 'datetime') {
                $data[$this->created_field] = date('Y-m-d H:i:s');
            } else {
                $data[$this->created_field] = time();
            }
        }

        if ($this->updated_field) {
            if ($this->updated_formatter == 'datetime') {
                $data[$this->updated_field] = date('Y-m-d H:i:s');
            } else {
                $data[$this->updated_field] = time();
            }
        }

        // 生成SQL语句
        $sql = $this->builder->insert($data, $options, $replace);
        // 获取参数绑定
        $bind = $this->getBind();
        // 判断调试模式,返回sql
        if (isset($options['debug']) && $options['debug']) {
            return $this->connection->getRealSql($sql, $bind);
        }

        // 执行操作
        $result = (false === $sql) ? false : $this->execute($sql, $bind);

        // 触发写入事件
        SeeDb::trigger('insert', $this->connection, $options);

        // 执行成功，判断是否返回自增ID
        if ($result && $getLastInsID) {
            return $this->getLastInsID($key);
        }

        return $result;
    }

    /**
     * 批量插入数据
     *
     * @param array $data 数据集
     * @param boolean $replace 是否replace
     * @return integer  影响行数
     */
    public function insertAll(array $data = [], bool $replace = false)
    {
        // 批量操作, 必须通过insertAll方法传递数组数据
        $options = $this->parseExpress();
        if (!is_array($data)) {
            return false;
        }
        // 生成SQL语句
        $sql = $this->builder->insertAll($data, $options, $replace);
        // 获取参数绑定
        $bind = $this->getBind();
        // 判断调试模式,返回sql
        if (isset($options['debug']) && $options['debug']) {
            return $this->connection->getRealSql($sql, $bind);
        }
        // 执行SQL
        $result = $this->execute($sql, $bind);
        // 触发写入事件
        SeeDb::trigger('insert', $this->connection, $options);

        return $result;
    }

    /**
     * 操作操作
     *
     * @return integer  影响行数
     * @throws SeeDbException
     */
    public function delete($id = null)
    {
        if ($id) {
            if (!is_array($id)) {
                $id = explode(',', $id . '');
            }
            $this->whereIn('id', $id);
        }

        $options = $this->parseExpress();

        if (empty($options['where'])) {
            // 操作操作，查询条件不能为空
            throw new SeeDbException(
                "The delete operation query condition cannot be empty!",
                SeeDbException::WHERE_IS_NULL
            );
        }
        // 生成删除SQL语句
        $sql = $this->builder->delete($options);
        // 获取参数绑定
        $bind = $this->getBind();
        // 判断调试模式,返回sql
        if (isset($options['debug']) && $options['debug']) {
            return $this->connection->getRealSql($sql, $bind);
        }
        // 执行SQL
        $result = $this->execute($sql, $bind);
        // 触发删除事件
        SeeDb::trigger('delete', $this->connection, $options);

        return $result;
    }

    /**
     * 调试模式,只返回SQL
     *
     * @return SeeDbQuery 当前实例自身
     */
    public function debug()
    {
        $this->options['debug'] = true;
        return $this;
    }

    /**
     * 获取PDO结果集,不解析
     *
     * @return SeeDbQuery 当前实例自身
     */
    public function getObj()
    {
        $this->options['obj'] = true;
        return $this;
    }

    /**
     * 设置表名(含表前缀)
     *
     * @param string $table 表名
     * @return SeeDbQuery    当前实例自身
     */
    public function table(string $table): SeeDbQuery
    {
        if (strpos($table, ',')) {
            // 多表
            $tables = explode(',', $table);
            $table = [];
            foreach ($tables as $item) {
                list($item, $alias) = explode(' ', trim($item));
                if ($alias) {
                    $this->alias([$item => $alias]);
                    $table[$item] = $alias;
                } else {
                    $table[] = $item;
                }
            }
        } elseif (strpos($table, ' ')) {
            list($table, $alias) = explode(' ', $table);
            $table = [$table => $alias];
            $this->alias($table);
        }

        $this->options['table'] = $this->table = $table;

        return $this;
    }

    /**
     * 指定数据表别名
     *
     * @param string|array $alias 数据表别名
     * @return SeeDbQuery    当前实例自身
     */
    public function alias($alias): SeeDbQuery
    {
        if (is_array($alias)) {
            foreach ($alias as $key => $val) {
                $table = $key;
                $this->options['alias'][$table] = $val;
            }
        } else {
            $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table'];
            $this->options['alias'][$table] = $alias;
        }

        return $this;
    }

    /**
     * 查询字符串
     *
     * @param mixed $field 查询字段
     * @return SeeDbQuery    当前实例自身
     */
    public function field($field): SeeDbQuery
    {
        if (empty($field)) {
            return $this;
        }

        if ($field instanceof SeeDbRaw) {
            $this->options['field'][] = $field;
            return $this;
        }

        if (is_string($field)) {
            $field = array_map('trim', explode(',', $field));
        }

        if ($field === true) {
            // 获取全部字段
            $field = ['*'];
        }

        if (isset($this->options['field'])) {
            $field = array_merge((array)$this->options['field'], $field);
        }

        $this->options['field'] = array_unique($field);
        return $this;
    }

    /**
     * 指定查询数量
     *
     * @param mixed $offset 起始位置
     * @param mixed $length 查询数量
     * @return SeeDbQuery    当前实例自身
     */
    public function limit($offset, $length = null): SeeDbQuery
    {
        if (is_null($length) && strpos($offset, ',')) {
            list($offset, $length) = explode(',', $offset);
        }

        $this->options['limit'] = intval($offset) . ($length ? ',' . intval($length) : '');

        return $this;
    }

    /**
     * 分页查询
     *
     * @param integer $page 当前页数，从1开始
     * @param integer $length 每页记录条数
     * @return SeeDbQuery  当前实例自身
     */
    public function page(int $page, int $length): SeeDbQuery
    {
        $page = $page > 0 ? ($page - 1) : 0;

        return $this->limit($page * $length, $length);
    }

    /**
     * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc'])
     *
     * @param string|array $field 排序字段
     * @param string $order 排序
     * @return SeeDbQuery    当前实例自身
     */
    public function order($field, string $order = ''): SeeDbQuery
    {
        if (empty($field)) {
            return $this;
        }

        if (is_string($field)) {
            if (strpos($field, ',')) {
                $field = array_map('trim', explode(',', $field));
            } else {
                $field = empty($order) ? $field : [$field => $order];
            }
        }

        if (!isset($this->options['order'])) {
            $this->options['order'] = [];
        }

        if (is_array($field)) {
            $this->options['order'] = array_merge($this->options['order'], $field);
        } else {
            $this->options['order'][] = $field;
        }

        return $this;
    }

    /**
     * 随机排序
     *
     * @return SeeDbQuery 当前实例自身
     */
    public function orderRand(): SeeDbQuery
    {
        $this->options['order'][] = '[rand]';

        return $this;
    }

    /**
     * 指定group查询
     *
     * @param string $group GROUP
     * @return SeeDbQuery    当前实例自身
     */
    public function group(string $group): SeeDbQuery
    {
        $this->options['group'] = $group;

        return $this;
    }

    /**
     * 指定having查询
     *
     * @param string $having having
     * @return SeeDbQuery    当前实例自身
     */
    public function having(string $having): SeeDbQuery
    {
        $this->options['having'] = $having;

        return $this;
    }

    /**
     * join查询SQL组装
     *
     * @param mixed $join 关联的表名
     * @param mixed $condition 条件
     * @param string $type JOIN类型
     * @return SeeDbQuery    当前实例自身
     */
    public function join($join, $condition = null, string $type = 'INNER'): SeeDbQuery
    {
        if (empty($condition)) {
            // 如果为组数，则循环调用join
            foreach ($join as $value) {
                if (is_array($value) && 2 <= count($value)) {
                    $this->join($value[0], $value[1], $value[2] ?? $type);
                }
            }
        } else {
            $table = $this->getJoinTable($join);

            $this->options['join'][] = [$table, strtoupper($type), $condition];
        }

        return $this;
    }

    /**
     * left join查询SQL组装
     *
     * @param mixed $join 关联的表名
     * @param mixed $condition 条件
     * @return SeeDbQuery    当前实例自身
     */
    public function leftJoin($join, $condition = null): SeeDbQuery
    {
        return $this->join($join, $condition, 'LEFT');
    }

    /**
     * right join查询SQL组装
     *
     * @param mixed $join 关联的表名
     * @param mixed $condition 条件
     * @return SeeDbQuery    当前实例自身
     */
    public function rightJoin($join, $condition = null): SeeDbQuery
    {
        return $this->join($join, $condition, 'RIGHT');
    }

    /**
     * USING支持 用于多表删除
     *
     * @param string|array $using USING
     * @return SeeDbQuery    当前实例自身
     */
    public function using($using): SeeDbQuery
    {
        $this->options['using'] = $using;

        return $this;
    }

    /**
     * 设置查询的额外参数
     *
     * @param string $extra 额外信息
     * @return SeeDbQuery    当前实例自身
     */
    public function extra(string $extra): SeeDbQuery
    {
        $this->options['extra'] = $extra;

        return $this;
    }

    /**
     * 设置DUPLICATE
     *
     * @param array|string $duplicate DUPLICATE信息
     * @return SeeDbQuery    当前实例自身
     */
    public function duplicate($duplicate): SeeDbQuery
    {
        $this->options['duplicate'] = $duplicate;

        return $this;
    }

    /**
     * 指定AND查询条件，支持：
     *  where('id = 1')->select();
     *  where('id', 1)->select();
     *  where('id', '=', 1)->select();
     *  where(['id' => 1])->select();
     *  where(['id' => ['=', 1]])->select();
     *
     * @param mixed $field 查询字段
     * @param mixed $op 查询表达式
     * @param mixed $condition 查询条件
     * @return SeeDbQuery    当前实例自身
     */
    public function where($field, $op = null, $condition = null): SeeDbQuery
    {
        $param = func_get_args();
        array_shift($param);
        $this->parseWhereExp('AND', $field, $op, $condition, $param);

        return $this;
    }

    /**
     * 指定OR查询条件
     *
     * @param mixed $field 查询字段
     * @param mixed $op 查询表达式
     * @param mixed $condition 查询条件
     * @return SeeDbQuery    当前实例自身
     */
    public function whereOr($field, $op = null, $condition = null): SeeDbQuery
    {
        $param = func_get_args();
        array_shift($param);
        $this->parseWhereExp('OR', $field, $op, $condition, $param);

        return $this;
    }


    /**
     * 设置数据
     *
     * @param string|array $field 字段名或者数据
     * @param mixed $value 字段值
     * @return SeeDbQuery    当前实例自身
     */
    public function data($field, $value = null): SeeDbQuery
    {
        if (is_array($field)) {
            $this->options['data'] = isset($this->options['data']) ? array_merge($this->options['data'], $field) : $field;
        } else {
            $this->options['data'][$field] = $value;
        }
        return $this;
    }

    /**
     * 字段值增长
     *
     * @param string|array $field 字段名
     * @param float $step 增长值
     * @return SeeDbQuery    当前实例自身
     */
    public function inc($field, $step = 1): SeeDbQuery
    {
        $fields = is_string($field) ? explode(',', $field) : $field;
        foreach ($fields as $field) {
            $this->data($field, ['inc', $step]);
        }
        return $this;
    }

    /**
     * 字段值减少
     *
     * @param string|array $field 字段名
     * @param float $step 增长值
     * @return SeeDbQuery    当前实例自身
     */
    public function dec($field, $step = 1): SeeDbQuery
    {
        $fields = is_string($field) ? explode(',', $field) : $field;
        foreach ($fields as $field) {
            $this->data($field, ['dec', $step]);
        }

        return $this;
    }

    /**
     * 查询lock
     *
     * @param boolean|string $lock 是否lock
     * @return SeeDbQuery    当前实例自身
     */
    public function lock($lock = false): SeeDbQuery
    {
        $this->options['lock'] = $lock;

        return $this;
    }

    /**
     * distinct查询
     *
     * @param string|boolean $distinct 是否去重
     * @return SeeDbQuery    当前实例自身
     */
    public function distinct($distinct = false): SeeDbQuery
    {
        $this->options['distinct'] = $distinct;

        return $this;
    }

    /**
     * 查询 union
     *
     * @param mixed $union
     * @param bool $all
     * @return SeeDbQuery    当前实例自身
     */
    public function union($union, bool $all = false): SeeDbQuery
    {
        $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION';

        if (is_array($union)) {
            $this->options['union'] = array_merge($this->options['union'], $union);
        } else {
            $this->options['union'][] = $union;
        }

        return $this;
    }

    /**
     * 指定强制索引
     *
     * @param string $force 索引名称
     * @return SeeDbQuery    当前实例自身
     */
    public function force(string $force): SeeDbQuery
    {
        $this->options['force'] = $force;

        return $this;
    }

    /**
     * 查询注释
     *
     * @param string $comment 注释
     * @return SeeDbQuery    当前实例自身
     */
    public function comment(string $comment): SeeDbQuery
    {
        $this->options['comment'] = $comment;

        return $this;
    }

    /**
     * 参数绑定
     *
     * @param mixed $key 参数名
     * @param mixed $value 绑定变量值
     * @param integer $type 绑定类型
     * @return SeeDbQuery    当前实例自身
     */
    public function bind($key, $value = false, int $type = PDO::PARAM_STR): SeeDbQuery
    {
        if (is_array($key)) {
            $this->bind = array_merge($this->bind, $key);
        } else {
            $this->bind[$key] = [$value, $type];
        }

        return $this;
    }

    /**
     * 检测参数是否已经绑定
     *
     * @param string $key 参数名
     * @return boolean
     */
    public function isBind(string $key): bool
    {
        return isset($this->bind[$key]);
    }

    /**
     * 获取绑定的参数 并清空
     *
     * @return array
     */
    public function getBind(): array
    {
        $bind = $this->bind;
        $this->bind = [];

        return $bind;
    }

    /**
     * 获取数据库的配置参数
     *
     * @param string $name 参数名称
     * @return mixed
     */
    public function getConfig(string $name = '')
    {
        return $this->connection->getConfig($name);
    }

    /**
     * 分析查询表达式
     *
     * @param string $logic 查询逻辑 and or xor
     * @param string|array $field 查询字段
     * @param mixed $op 查询表达式
     * @param mixed $condition 查询条件
     * @param array $param 查询参数
     * @param boolean $strict 严格模式
     * @return void
     */
    protected function parseWhereExp(string $logic, $field, $op, $condition, array $param = [], bool $strict = false)
    {
        $logic = strtoupper($logic);
        if ($field instanceof SeeDbRaw) {
            $this->options['where'][$logic][] = is_string($op) ? [$op, $field] : $field;
            return;
        }

        if ($strict) {
            // 使用严格模式查询
            $where[$field] = [$op, $condition];
            // 记录一个字段多次查询条件
            $this->options['multi'][$logic][$field][] = $where[$field];
        } elseif (is_null($op) && is_null($condition)) {
            if (is_array($field)) {
                // 数组批量查询
                $where = $field;
                foreach ($where as $k => $val) {
                    $this->options['multi'][$logic][$k][] = $val;
                }
            } elseif ($field && is_string($field)) {
                // 字符串查询
                if (preg_match('/[,=\<\'\"\(\s]/', $field)) {
                    // 手写where条件，不做处理，直接写入
                    $this->options['where'][$logic][] = $field;
                } else {
                    $where[$field] = ['null', ''];
                    $this->options['multi'][$logic][$field][] = $where[$field];
                }
            }
        } elseif (is_array($op)) {
            $where[$field] = $param;
        } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) {
            // null查询
            $where[$field] = [$op, ''];
            $this->options['multi'][$logic][$field][] = $where[$field];
        } elseif (is_null($condition)) {
            // 字段相等查询
            $where[$field] = ['=', $op];
            $this->options['multi'][$logic][$field][] = $where[$field];
        } else {
            // 记录一个字段多次查询条件
            $where[$field] = [$op, $condition];
            $this->options['multi'][$logic][$field][] = $where[$field];
        }

        if (!empty($where)) {
            if (!isset($this->options['where'][$logic])) {
                $this->options['where'][$logic] = [];
            }
            if (is_string($field) && $this->checkMultiField($field, $logic)) {
                $where[$field] = $this->options['multi'][$logic][$field];
            } elseif (is_array($field)) {
                foreach ($field as $key => $val) {
                    if ($this->checkMultiField($key, $logic)) {
                        $where[$key] = $this->options['multi'][$logic][$key];
                    }
                }
            }
            $this->options['where'][$logic] = array_merge($this->options['where'][$logic], $where);
        }
    }

    /**
     * 解析表达式（可用于查询或者写入操作）
     * @return array
     * @throws SeeDbException
     */
    protected function parseExpress(): array
    {
        $options = $this->options;

        if (empty($options['table'])) {
            throw new SeeDbException(
                'The query table is not set!',
                SeeDbException::TABLE_NULL_FOUND
            );
        }

        if (!isset($options['where'])) {
            $options['where'] = [];
        }

        if (!isset($options['field'])) {
            $options['field'] = '*';
        }

        if (!isset($options['data'])) {
            $options['data'] = [];
        }

        foreach (['lock', 'distinct'] as $name) {
            if (!isset($options[$name])) {
                $options[$name] = false;
            }
        }

        foreach (['join', 'union', 'group', 'having', 'limit', 'order', 'force', 'comment', 'extra', 'using', 'duplicate'] as $name) {
            if (!isset($options[$name])) {
                $options[$name] = '';
            }
        }

        $this->options = [];

        return $options;
    }

    /**
     * 获取Join表名及别名 支持
     * ['prefix_table或者子查询'=>'alias'] 'prefix_table alias' 'table alias'
     *
     * @param array|string $join
     * @return array|string
     */
    protected function getJoinTable($join, &$alias = null)
    {
        // 传入的表名为数组
        if (is_array($join)) {
            $table = $join;
            $alias = array_shift($join);
        } else {
            $join = trim($join);
            if (false !== strpos($join, '(')) {
                // 使用子查询
                $table = $join;
            } else {
                if (strpos($join, ' ')) {
                    // 使用别名
                    list($table, $alias) = explode(' ', $join);
                } else {
                    $table = $join;
                    if (false === strpos($join, '.') && 0 !== strpos($join, '__')) {
                        $alias = $join;
                    }
                }
            }
            if (isset($alias) && $table != $alias) {
                $table = [$table => $alias];
            }
        }
        return $table;
    }

    /**
     * 检查是否存在一个字段多次查询条件
     *
     * @param string $field 查询字段
     * @param string $logic 查询逻辑 and or xor
     * @return bool
     */
    protected function checkMultiField(string $field, string $logic): bool
    {
        return isset($this->options['multi'][$logic][$field]) && count($this->options['multi'][$logic][$field]) > 1;
    }

    /**
     * 获取Builder类对象实例
     *
     * @return SeeDbBuilder 查询语句构造器实例
     */
    protected function getBuilder(): SeeDbBuilder
    {
        return new SeeDbBuilder($this->connection, $this);
    }
}
